Skip to content

Package constants#8916

Merged
asfernandes merged 133 commits into
FirebirdSQL:masterfrom
Noremos:metacache_package_constants
May 11, 2026
Merged

Package constants#8916
asfernandes merged 133 commits into
FirebirdSQL:masterfrom
Noremos:metacache_package_constants

Conversation

@Noremos
Copy link
Copy Markdown
Contributor

@Noremos Noremos commented Feb 25, 2026

Package Constants

This PR adds a new database object - Package Constant (#1036). It is a constant value located in a package header (public visibility) or a package body (private visibility). See README.packages.txt for more information.

SYNTAX

Creation:

CREATE PACKAGE <package_name>
AS
BEGIN
    CONSTANT <constant_name> <type> = <constant_expr>; -- public constant
    -- package routines
END

CREATE PACKAGE BODY <package_name>
AS
BEGIN
    CONSTANT <constant_name> <type> = <constant_expr>; -- private constant
    -- package routines
END

<constant_expr> is an expression that remains unchanged after recompilation.

Package constants can be queried using the expression <package_name>.<consatnt_name> outside the package (<consatnt_name> must be a public constant) and using the expression <consatnt_name> inside the package.
To query a constant, the user/role must have USAGE permission on the package.

Other SQL extensions:

COMMENT ON CONSTANT [<schema>.]<package>.<const_name> IS <text>
GRANT USAGE ON PACKAGE  [<schema>.]<package_name> to [<user|role>] <name> [<grant_option>] [<granted_by>]
REVOKE USAGE ON PACKAGE  [<schema>.]<package_name> FROM <user|role> <name> [<granted_by>]
SHOW CONST[ANTS]  [ [<schema>.]<package_name> ] -- isql only

Usage examples:

set autoterm;
CREATE PACKAGE MY_PACKAGE
AS
BEGIN
    CONSTANT PUBLIC_CONST INTEGER = 10;
    FUNCTION MY_SECRET_PRINT() RETURNS INT;
END;

CREATE PACKAGE BODY MY_PACKAGE
AS
BEGIN
    CONSTANT SECRET_1 INTEGER = 15;
    CONSTANT SECRET_2 INTEGER = 30;

    FUNCTION MY_SECRET_PRINT() RETURNS INT
    AS
    BEGIN
        RETURN SECRET_1 * SECRET_2;
    END
END;
commit;

select MY_PACKAGE.PUBLIC_CONST from rdb$database ;
select MY_PACKAGE.MY_SECRET_PRINT() from rdb$database;

System Constants

As an exmaple, 3 consatnts have been added to the RDB$BLOB_UTIL package

FROM_BEGIN  : INTEGER = 0
FROM_CURRENT : INTEGER = 1
FROM_END : INTEGER = 2

System metadata changes

New system table - RDB$CONSTANTS

Table: SYSTEM.RDB$CONSTANTS
RDB$CONSTANT_NAME               (SYSTEM.RDB$FIELD_NAME) CHAR(63) CHARACTER SET SYSTEM.UTF8 
RDB$PACKAGE_NAME                (SYSTEM.RDB$PACKAGE_NAME) CHAR(63) CHARACTER SET SYSTEM.UTF8 Nullable 
RDB$FIELD_SOURCE                (SYSTEM.RDB$FIELD_NAME) CHAR(63) CHARACTER SET SYSTEM.UTF8 
RDB$FIELD_SOURCE_SCHEMA_NAME    (SYSTEM.RDB$SCHEMA_NAME) CHAR(63) CHARACTER SET SYSTEM.UTF8 
RDB$PRIVATE_FLAG                (SYSTEM.RDB$SYSTEM_NULLFLAG) SMALLINT 
RDB$CONSTANT_BLR                (SYSTEM.RDB$CONSTANT_BLR) BLOB segment 80, subtype BLR 
RDB$CONSTANT_SOURCE             (SYSTEM.RDB$SOURCE) BLOB segment 80, subtype TEXT CHARACTER SET SYSTEM.UTF8 Nullable 
RDB$SCHEMA_NAME                 (SYSTEM.RDB$SCHEMA_NAME) CHAR(63) CHARACTER SET SYSTEM.UTF8 
RDB$DESCRIPTION                 (SYSTEM.RDB$DESCRIPTION) BLOB segment 80, subtype TEXT CHARACTER SET SYSTEM.UTF8 Nullable 
CONSTRAINT RDB$INDEX_98:
  Unique key (RDB$SCHEMA_NAME, RDB$PACKAGE_NAME, RDB$CONSTANT_NAME)

A new field has been added to RDB$PACKAGES - RDB$PACKAGE_ID

New indexes:

Unique key SYSTEM.RDB$CONSTANTS  (RDB$SCHEMA_NAME, RDB$PACKAGE_NAME, RDB$CONSTANT_NAME)
Unique key SYSTEM.RDB$PACKAGES (RDB$PACKAGE_ID)

Implementation

Packages have been added to the metacaching system. Since it relies heavily on identifiers, a new ID field (RDB$PACKAGE_ID) has been added to the RDB$PACKAGES table. Constants are stored as an array in the package metacaching object, with a constant_name-to-array_id mapping.

There are also two important points:

  1. All nodes now have an 'is constant' flag, similar to the 'is deterministic' flag. This is necessary to determine whether an expression can initialize a constant.
  2. In the CreateAlterPackageNode node, a lot of code had to be copied and pasted, and adding constants led to complete chaos. Therefore, I decided to slightly refactor this node. All functionality remains the same; only code duplication was removed.

@sim1984
Copy link
Copy Markdown
Contributor

sim1984 commented Feb 25, 2026

Are comments supported for constants? I didn't see this in the documentation.
Let me clarify what I mean:

COMMENT ON CONSTANT [<schema>.]<package>.<const_name> IS 'Text'

Comments are simply supported for packaged procedures and functions. It would make sense to do the same for constants (and any package objects, for that matter).

COMMENT ON PROCEDURE [<schema>.]<package>.<proc_name> IS 'Text';
COMMENT ON FUNCTION [<schema>.]<package>.<func_name> IS 'Text';

@Noremos
Copy link
Copy Markdown
Contributor Author

Noremos commented Feb 25, 2026

Are comments supported for constants?

No, I didn't plan to implement comments, but I think it's possible. I will take a look

@sim1984
Copy link
Copy Markdown
Contributor

sim1984 commented May 7, 2026

I think the README.packages.txt documentation should include a more detailed explanation of which expressions can and cannot initialize a constant. People should know this without having to look at the code.

@Noremos
Copy link
Copy Markdown
Contributor Author

Noremos commented May 7, 2026

I think the README.packages.txt documentation should include a more detailed explanation of which expressions can and cannot initialize a constant. People should know this without having to look at the code.

I add a list with all valid constant operations.

@asfernandes
Copy link
Copy Markdown
Member

I was going to test if a constant with exception could be created (it should not) and an assert happened:

SQL> create package pkg as begin constant c1 integer = 1 / 0; end!
Assertion (!child) failure: /home/asfernandes/fb/dev/firebird-master.git/src/common/classes/alloc.cpp 2075

Thread 1 "isql" received signal SIGABRT, Aborted.
__pthread_kill_implementation (threadid=<optimized out>, signo=6, no_tid=0) at ./nptl/pthread_kill.c:44
warning: 44	./nptl/pthread_kill.c: No such file or directory
(gdb) bt
#0  __pthread_kill_implementation (threadid=<optimized out>, signo=6, no_tid=0) at ./nptl/pthread_kill.c:44
#1  __pthread_kill_internal (threadid=<optimized out>, signo=6) at ./nptl/pthread_kill.c:89
#2  __GI___pthread_kill (threadid=<optimized out>, signo=signo@entry=6) at ./nptl/pthread_kill.c:100
#3  0x00007ffff7645e2e in __GI_raise (sig=sig@entry=6) at ../sysdeps/posix/raise.c:26
#4  0x00007ffff7628888 in __GI_abort () at ./stdlib/abort.c:77
#5  0x00007ffff50e5077 in fb_assert_impl (msg=0x7ffff5a35ee7 "!child", file=0x7ffff5a35e92 "/home/asfernandes/fb/dev/firebird-master.git/src/common/classes/alloc.cpp", 
    line=2075, do_abort=true) at /home/asfernandes/fb/dev/firebird-master.git/src/include/../common/gdsassert.h:48
#6  0x00007ffff5887354 in Firebird::MemPool::~MemPool (this=0x7fffeeebc050) at /home/asfernandes/fb/dev/firebird-master.git/src/common/classes/alloc.cpp:2075
#7  0x00007ffff5887a29 in Firebird::MemPool::~MemPool (this=0x7fffeeebc050) at /home/asfernandes/fb/dev/firebird-master.git/src/common/classes/alloc.cpp:2015
#8  0x00007ffff5888944 in Firebird::MemPool::deletePool (pool=0x7fffeeebc050) at /home/asfernandes/fb/dev/firebird-master.git/src/common/classes/alloc.cpp:2635
#9  0x00007ffff5889030 in Firebird::MemoryPool::deletePool (pool=0x7fffef496c90) at /home/asfernandes/fb/dev/firebird-master.git/src/common/classes/alloc.cpp:2796
#10 0x00007ffff5134997 in Jrd::Database::deletePool (this=0x7ffff4fc5550, pool=0x7fffef496c90) at /home/asfernandes/fb/dev/firebird-master.git/src/jrd/Database.cpp:195
#11 0x00007ffff51e3606 in Jrd::ConstantValue::makeValue(Jrd::thread_db*, Jrd::Request*)::$_0::operator()() const (this=0x7fffffff9728)
    at /home/asfernandes/fb/dev/firebird-master.git/temp/debug/temp/Debug/jrd/Package.cpp:574
#12 0x00007ffff51e2665 in Firebird::Cleanup<Jrd::ConstantValue::makeValue(Jrd::thread_db*, Jrd::Request*)::$_0>::~Cleanup (this=0x7fffffff9728)
    at /home/asfernandes/fb/dev/firebird-master.git/src/include/../common/classes/auto.h:345
#13 0x00007ffff51e2590 in Jrd::ConstantValue::makeValue (this=0x7fffef496b10, tdbb=0x7fffffffa8b0, request=0x7fffeeec45a0)
    at /home/asfernandes/fb/dev/firebird-master.git/temp/debug/temp/Debug/jrd/Package.cpp:632

@Noremos
Copy link
Copy Markdown
Contributor Author

Noremos commented May 8, 2026

I was going to test if a constant with exception could be created (it should not) and an assert happened:

Hmm, it looks like the pool shouldn't be deleted. I fixed it

@dyemanov
Copy link
Copy Markdown
Member

dyemanov commented May 8, 2026

I was going to test if a constant with exception could be created (it should not) and an assert happened:

Hmm, it looks like the pool shouldn't be deleted. I fixed it

It should, but not always. If exception is raised from MET_parse_blob, you should delete the pool. After that it becomes the statement pool and should not be released independently, only when the statement dies. If exception is raised from EVL_expr, another level of cleanup is needed - detach request from transaction and attachment and supposedly delete the statement too (it will delete the pool under the hood).

@Noremos
Copy link
Copy Markdown
Contributor Author

Noremos commented May 8, 2026

It should, but not always. If exception is raised from MET_parse_blob, you should delete the pool. After that it becomes the statement pool and should not be released independently, only when the statement dies. If exception is raised from EVL_expr, another level of cleanup is needed - detach request from transaction and attachment and supposedly delete the statement too (it will delete the pool under the hood).

Thanks for the explanation. I tried calling EXE_release(tdsb, request); but it results in the same assert that Adriano mentioned, but when at detach stage.

@dyemanov
Copy link
Copy Markdown
Member

dyemanov commented May 8, 2026

Thanks for the explanation. I tried calling EXE_release(tdsb, request); but it results in the same assert that Adriano mentioned, but when at detach stage.

I'd expect something like this:

catch (const Exception&)
{
    if (handmadeStmt)
        handmadeStmt->release(tdbb);
    else
        deletePool(csb_pool);

    throw;
}

@Noremos
Copy link
Copy Markdown
Contributor Author

Noremos commented May 9, 2026

Thanks for the explanation. I tried calling EXE_release(tdsb, request); but it results in the same assert that Adriano mentioned, but when at detach stage.

I'd expect something like this:

catch (const Exception&)
{
    if (handmadeStmt)
        handmadeStmt->release(tdbb);
    else
        deletePool(csb_pool);

    throw;
}

Thank you. This works great

Comment thread src/jrd/Resources.cpp
Comment thread src/burp/restore.epp

ITransaction* local_trans = tdgbl->global_trans ? tdgbl->global_trans : gds_trans;

STORE (TRANSACTION_HANDLE local_trans
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Conditional store of value in fields without set they .NULL = TRUE before will make them always non-null, with garbage when the conditional paths are not executed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm really confused about the NULL spcificatin. Many places in the code explicitly set <field>.NULL = FALSE, so I assumed it was TRUE by default. But, it seems like I was wrong
Am I understanding the rules correctly:

  1. If NULL isn't explicitly specified, NULL=FALSE is set;
  2. If NULL is explicitly present but not specified, NULL=FALSE is set.

Comment thread src/jrd/SysFunction.cpp Outdated
@asfernandes asfernandes merged commit 7589a56 into FirebirdSQL:master May 11, 2026
23 checks passed
@sim1984
Copy link
Copy Markdown
Contributor

sim1984 commented May 12, 2026

System package constants are not available.

select
  RDB$BLOB_UTIL.FROM_BEGIN
from RDB$DATABASE;
Statement failed, SQLSTATE = 42S22
Dynamic SQL Error
-SQL error code = -206
-Column unknown
-"RDB$BLOB_UTIL"."FROM_BEGIN"
-At line 2, column 3

The reason may be that the RDB$BLOB_UTIL package has not been granted the USAGE privilege.

select
  P.RDB$USER,
  P.RDB$PRIVILEGE
from RDB$USER_PRIVILEGES P
where p.RDB$RELATION_NAME = 'RDB$BLOB_UTIL';
RDB$USER                                                        RDB$PRIVILEGE
=============================================================== =============
SYSDBA                                                          X
PUBLIC                                                          X

There should be two more entries here, in theory.

SYSDBA                                                          G
PUBLIC                                                          G

@dyemanov dyemanov linked an issue May 12, 2026 that may be closed by this pull request
@aafemt
Copy link
Copy Markdown
Contributor

aafemt commented May 12, 2026

There should be two more entries here, in theory.

...and an error message explicitly mentioning missing rights.

@sim1984
Copy link
Copy Markdown
Contributor

sim1984 commented May 12, 2026

There should be two more entries here, in theory.

...and an error message explicitly mentioning missing rights.

It's not necessarily a privilege issue. However, according to the description, constants require the USAGE privilege, while executing procedures and functions requires the EXECUTE privilege. When the database was created, the EXECUTE privilege was granted to the package for SYSDBA and PUBLIC, so it would be logical to expect the USAGE privilege to be present here as well.

@Noremos
Copy link
Copy Markdown
Contributor Author

Noremos commented May 12, 2026

System package constants are not available.

select
  RDB$BLOB_UTIL.FROM_BEGIN
from RDB$DATABASE;
Statement failed, SQLSTATE = 42S22
Dynamic SQL Error
-SQL error code = -206
-Column unknown
-"RDB$BLOB_UTIL"."FROM_BEGIN"
-At line 2, column 3

The reason may be that the RDB$BLOB_UTIL package has not been granted the USAGE privilege.

select
  P.RDB$USER,
  P.RDB$PRIVILEGE
from RDB$USER_PRIVILEGES P
where p.RDB$RELATION_NAME = 'RDB$BLOB_UTIL';
RDB$USER                                                        RDB$PRIVILEGE
=============================================================== =============
SYSDBA                                                          X
PUBLIC                                                          X

There should be two more entries here, in theory.

SYSDBA                                                          G
PUBLIC                                                          G

The RDB$BLOB_UTIL is in the SYSTEM schema. Therefore, it must be explicitly present in the query:

select
  SYSTEM.RDB$BLOB_UTIL.FROM_BEGIN
from RDB$DATABASE;

@aafemt
Copy link
Copy Markdown
Contributor

aafemt commented May 12, 2026

RDB$DATABASE also is in SYSTEM schema, still it is found because SYSTEM is in the default schema search path.

@sim1984
Copy link
Copy Markdown
Contributor

sim1984 commented May 12, 2026

The RDB$BLOB_UTIL is in the SYSTEM schema. Therefore, it must be explicitly present in the query:

select
  SYSTEM.RDB$BLOB_UTIL.FROM_BEGIN
from RDB$DATABASE;

This works, but I agree with @aafemt comment, the SYSTEM schema is in the search path, so the constant should be available according to the general rule.

@Noremos
Copy link
Copy Markdown
Contributor Author

Noremos commented May 12, 2026

The RDB$BLOB_UTIL is in the SYSTEM schema. Therefore, it must be explicitly present in the query:

select
  SYSTEM.RDB$BLOB_UTIL.FROM_BEGIN
from RDB$DATABASE;

This works, but I agree with @aafemt comment, the SYSTEM schema is in the search path, so the constant should be available according to the general rule.

It looks like I misunderstood the code and used the wrong schema search function.. I will create a PR with a fix

@sim1984
Copy link
Copy Markdown
Contributor

sim1984 commented May 12, 2026

And the second question: I'm creating a constant in a package, and for some reason I can't see the source code for its initialization expression.

set term ^;

recreate package p_cosnt_test
as
begin
  constant SOME_CONST INTEGER = 1;
end^

recreate package body p_cosnt_test
as
begin

end^

set term ;^

select
  RDB$CONSTANT_NAME,
  RDB$CONSTANT_BLR,
  RDB$CONSTANT_SOURCE
from RDB$CONSTANTS
WHERE RDB$PACKAGE_NAME = 'P_COSNT_TEST'
  AND RDB$SCHEMA_NAME = 'PUBLIC';
RDB$CONSTANT_NAME               SOME_CONST                                                                                                                                                                                                      
RDB$CONSTANT_BLR                3b:8
BLOB display set to subtype 1. This BLOB: subtype = 2
RDB$CONSTANT_SOURCE             <null>

Why is RDB$CONSTANT_SOURCE = NULL?

@Noremos
Copy link
Copy Markdown
Contributor Author

Noremos commented May 12, 2026

And the second question: I'm creating a constant in a package, and for some reason I can't see the source code for its initialization expression.

Good question. It looks like I lost RDB$CONSTANT_SOURCE storing functional during the Metacache merge. I'll add this to the pull request.

@aafemt
Copy link
Copy Markdown
Contributor

aafemt commented May 12, 2026

This works

So, USAGE privilege is actually not needed and EXECUTE is enough? The description is wrong?

@Noremos
Copy link
Copy Markdown
Contributor Author

Noremos commented May 12, 2026

This works

So, USAGE privilege is actually not needed and EXECUTE is enough? The description is wrong?

No, the usage is necessary, but I'm not sure how it should be provided to SYSTEM packages.

Database: employee, User: A
SQL> select RDB$BLOB_UTIL.FROM_END from rdb$database;
Statement failed, SQLSTATE = 28000
no permission for USAGE access to PACKAGE "SYSTEM"."RDB$BLOB_UTIL"
-Effective user is A

@sim1984
Copy link
Copy Markdown
Contributor

sim1984 commented May 12, 2026

The USAGE privilege is indeed necessary. The only thing I'd like to point out is that privileges aren't checked for SYSDBA anyway, so they don't need to be created. However, I would prefer consistent behavior for all privilege types. That is, if the EXECUTE privilege is created for SYSDBA for each package, then the same should be true for USAGE.

@Noremos
Copy link
Copy Markdown
Contributor Author

Noremos commented May 12, 2026

The USAGE privilege is indeed necessary. The only thing I'd like to point out is that privileges aren't checked for SYSDBA anyway, so they don't need to be created. However, I would prefer consistent behavior for all privilege types. That is, if the EXECUTE privilege is created for SYSDBA for each package, then the same should be true for USAGE.

I added USAGE privilage for all system packages.
The PR with all fixes: #9022

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow to define const(s) to database

10 participants